using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace TankWars
{
    public class Tank : MovableSprite
    {
        public const int N_MAX_HEALTH = 100;

        #region Private Variables
        private Weapons.WeaponManager weaponManager;

        public TankTurret Turret;

        private const float topSpeed = 100.0f;        //Top speed moving forward. 
        private const float reverseSpeed = 60.0f;    //Speed in reverse.
        private const float angularAccel = 2;      //Turn speed.
        private const float friction = 5.0f;       //The coeficient of friction.

        //These variables are calculated based on the
        //desired topSpeed with the given friction.
        private float enginePower;    //The engine's pick up strength.
        private float engineReverse;  //Engine's power in reverse.

        private bool inReverse = false;  //Determines if the tank is moving in reverse.

        private float fSpeed = 50.0f;
        private float fAcceleration = 15.0f;
        private Vector2 vLastPosition;

        #region Tank Status Variables
        private int nHealth = N_MAX_HEALTH;
        private Player tankPlayer;
        #endregion
        #endregion

        #region Properties
        /// <summary>
        /// Sets the speed and rotation of the tank based on the given direction
        /// </summary>
        public int tankWidth
        {
            get
            {
                //Subtract 1/3 width to negate the turrent.
                float noTurrent = this.Sprite.Width - (float)(this.Sprite.Width / 3.00);

                //Round by adding 0.5 and casting to int.
                return (int)(noTurrent+0.5);
            }
        }
        public int tankHeight
        {
            get
            {
                return this.Sprite.Height;
            }
        }

        public new Vector2 Origin
        {
            get
            {
                Vector2 origin = new Vector2();
                origin.X = (this.tankWidth) / 2;
                origin.Y = (this.tankHeight) / 2;

                return origin;
            }
        }

        public Vector2 Speed
        {
            get
            {
                return base.vSpeed;
            }
        }
        public float Rotation
        {
            get
            {
                return base.fRotation;
            }
        }

        /// <summary>
        /// The color tint of the tank sprite
        /// </summary>
        public Color TankColor
        {
            get
            {
                return base.cSpriteColor;
            }
        }
        /// <summary>
        /// The tank sprite that will be drawn
        /// </summary>
        public Texture2D TankSprite
        {
            get
            {
                return base.Sprite;
            }
            set
            {
                base.Sprite = value;
            }
        }

        /// <summary>
        /// Gets the amount of health left
        /// </summary>
        public int Health
        {
            get
            {
                return this.nHealth;
            }
            set
            {
                this.nHealth = value;
            }
        }
        /// <summary>
        /// Gets the player that owns the tank
        /// </summary>
        public Player TankPlayer
        {
            get
            {
                return this.tankPlayer;
            }
        }

        public Weapons.WeaponManager WeaponManager
        {
            get
            {
                return this.weaponManager;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor for a new tank
        /// </summary>
        /// <param name="TankSprite">The tank sprite for the tank</param>
        public Tank(Texture2D TankSprite, Texture2D TankTurretSprite, Player tankPlayer, Dictionary<Type, Texture2D> shotTextures, Dictionary<Type, Texture2D> shotIcons)
        {
            base.Sprite = TankSprite;

            this.tankPlayer = tankPlayer;

            this.weaponManager = new TankWars.Weapons.WeaponManager(this, shotTextures, shotIcons);

            this.Turret = new TankTurret(TankTurretSprite, this);

            //The force exerted by the engine will be equal
            //to the force of friction when the tank is
            //moving at full speed.  The force of friction
            //is the friction coeficient times the speed.
            enginePower = (topSpeed) * (friction);
            engineReverse = (reverseSpeed) * (friction);
        }

        /// <summary>
        /// Constructor for a new tank with the given color
        /// </summary>
        /// <param name="TankSprite">The tank sprite for the tank</param>
        /// <param name="TankColor">The color tint of the tank</param>
        public Tank(Texture2D TankSprite, Texture2D TankTurretSprite, Color TankColor, Player tankPlayer, Dictionary<Type, Texture2D> shotTextures, Dictionary<Type, Texture2D> shotIcons)
        {
            base.Sprite = TankSprite;
            base.cSpriteColor = TankColor;

            this.tankPlayer = tankPlayer;

            this.weaponManager = new TankWars.Weapons.WeaponManager(this, shotTextures, shotIcons);

            this.Turret = new TankTurret(TankTurretSprite, this);

            //The force exerted by the engine will be equal
            //to the force of friction when the tank is
            //moving at full speed.  The force of friction
            //is the friction coeficient times the speed.
            enginePower = (topSpeed) * (friction);
            engineReverse = (reverseSpeed) * (friction);
        }
        #endregion

        #region Tank Controls
        
        /// <summary>
        /// Turns the tank clockwise.
        /// </summary>
        public void turnRight()
        {
            //Rotate the tank clockwise by 1 degree.
            base.fRotation += MathHelper.ToRadians(angularAccel);

            //Reset the rotation if it is > 2Pi
            if (this.fRotation > 2 * Math.PI)
                this.fRotation -= MathHelper.ToRadians(360);

            //Update the velocity so it is in the direction of the tank.
            float velocity = vSpeed.Length();
            this.vSpeed.X = (float)(velocity * Math.Cos(this.fRotation));
            this.vSpeed.Y = (float)(velocity * Math.Sin(this.fRotation));

            //Change direction of velocity if tank is in reverse
            if (inReverse)
            {
                this.vSpeed.X *= (-1);
                this.vSpeed.Y *= (-1);
            }
        }

        /// <summary>
        /// Turns the tank counter clockwise.
        /// </summary>
        public void turnLeft()
        {
            //Rotate the tank clockwise by 1 degree.
            base.fRotation -= MathHelper.ToRadians(angularAccel);

            //Reset the rotation if it is negative
            if (this.fRotation < 0)
                this.fRotation += MathHelper.ToRadians(360);

            //Update the velocity so it is in the direction of the tank.
            float velocity = vSpeed.Length();
            this.vSpeed.X = (float)(velocity * Math.Cos(this.fRotation));
            this.vSpeed.Y = (float)(velocity * Math.Sin(this.fRotation));

            //Change direction of velocity if tank is in reverse
            if (inReverse)
            {
                this.vSpeed.X *= (-1);
                this.vSpeed.Y *= (-1);
            }
        }

        /// <summary>
        /// Accelerates the tank in its current direction
        /// </summary>
        public void accelerate()
        {
            //Set in Reverse variable to false.
            inReverse = false;

            this.vAccel.X = (float)(enginePower * Math.Cos(this.fRotation));
            this.vAccel.Y = (float)(enginePower * Math.Sin(this.fRotation));
        }

        /// <summary>
        /// Makes the tank move in reverse.
        /// </summary>
        public void reverse()
        {
            //Set in Reverse variable to true.
            inReverse = true;

            //Acceleration equasions for moving in reverse.
            this.vAccel.X = (float)(engineReverse * Math.Cos(this.fRotation) * (-1));
            this.vAccel.Y = (float)(engineReverse * Math.Sin(this.fRotation) * (-1));
        }

        /// <summary>
        /// Fires a new shot
        /// </summary>
        public void FirePrimary(GameTime gameTime)
        {
            this.weaponManager.FirePrimary(gameTime);
        }

        public void FireSecondary(GameTime gameTime)
        {
            this.weaponManager.FireSecondary(gameTime);
        }
        #endregion

        #region Update Positions
        /// <summary>
        /// Applies friction to slow the tank
        /// </summary>
        public void applyFriction()
        {
            float velocity = vSpeed.Length();
            if (velocity < .01)//If the velocity is low, just set it to zero
            {
                vSpeed = Vector2.Zero;
            }
            else
            {
                //Friction will always appose the direction of movement.
                //The tank will not always be moving in the direction it
                //is aiming.  (Ex. if it collides with a block)
                //Therefore you must take the Arc tangent of the X and Y velocities.
                vAccel.X -= (float)((friction * velocity) * Math.Cos(Math.Atan2(vSpeed.Y, vSpeed.X)));
                vAccel.Y -= (float)((friction * velocity) * Math.Sin(Math.Atan2(vSpeed.Y, vSpeed.X)));
            }
        }

        /// <summary>
        /// Updates the position of the tank and all it's fired shots
        /// </summary>
        /// <param name="gameTime"></param>
        public void UpdatePosition(GameTime gameTime, Microsoft.Xna.Framework.Input.MouseState mousestate, Map map)
        {
            this.UpdateTankPosition(gameTime);
            this.UpdateShotPositions(gameTime);

            this.Turret.UpdatePosition(mousestate, map);
        }

        /// <summary>
        /// Updates the position of the tank
        /// </summary>
        /// <param name="gametime"></param>
        public void UpdateTankPosition(GameTime gameTime)
        {
            //Apply friction to thetank
            applyFriction();

            //Get previous Speed.
            Vector2 vSpeed0 = this.vSpeed;

            //Update the object's speed.
            this.vSpeed += this.vAccel * (float)gameTime.ElapsedGameTime.TotalSeconds;

            //Calculate the average velocity.
            Vector2 vAvgSpeed = (this.vSpeed + vSpeed0) / 2;

            //Determine the object's new position.
            this.vLastPosition = this.vPosition; //First save last position.
            this.vPosition += vAvgSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;

            //Reset Acceleration to zero.
            vAccel = Vector2.Zero;
        }

        /// <summary>
        /// Updates the position of all fired shots
        /// </summary>
        /// <param name="gameTime"></param>
        public void UpdateShotPositions(GameTime gameTime)
        {
            weaponManager.Update(gameTime);
        }
        #endregion

        #region Collision Functions
        ///<summary>
        ///Determines if a game object with given coordinates and dimensions has collided with the tank.
        ///</summary>
        public bool tankCollide(int objLeft, int objTop, int width, int height)
        {
            //The right and left side of the tank.
            float tankRight = this.vPosition.X + this.tankWidth/2;
            float tankLeft = this.vPosition.X - this.tankWidth/2;

            //Bottom and top of the tank.
            float tankBottom = this.vPosition.Y + this.tankHeight/2;
            float tankTop = this.vPosition.Y - this.tankHeight/2;

            int objRight = width + objLeft;   //Right side of object.
            int objBottom = objTop + height; //Bottome side of object.

            //Check collision by using bounding boxes.
            if ((tankRight > objLeft) && (tankLeft < objRight))
            {
                if ((tankBottom > objTop) && (tankTop < objBottom))
                {
                    return true;
                }
            }

            return false;
        }

        public bool tankToTankCollide(Tank tank)
        {
            return true;
        }

        public bool tankToShotCollide(Weapons.Shot shot)
        {
            return true;
        }

        ///<summary>
        ///Returns the angle (degrees) in which the object collided with the tank.
        ///</summary>
        public float collideAngle(float objLeft, float objTop, int width, int height)
        {
            float angle=0;

            //Get the coordinates of the center of the object.
            float objectX = objLeft + width / 2;
            float objectY = objTop + height / 2;

            //Use arc tangent to get the angle.
            angle = (float)Math.Atan2(this.vPosition.Y - objectY, objectX - this.vPosition.X);
            angle = angle * (float)(180 / Math.PI);    //Covert to degrees.

            //Ensure that the angle is in the proper quadrant based
            //on the given coordinates of the object and tank.

            //First Quadrant
            if ((objectX >= this.vPosition.X)&&(objectY <= this.vPosition.Y))
            {
                while ((angle > 90) || (angle < 0))
                {
                    angle += 90;
                    if (angle > 360)
                        angle -= 360;
                }
            }
            //Second Quadrant
            else if ((objectX <= this.vPosition.X)&&(objectY<= this.vPosition.Y))
            {
                while ((angle > 180) || (angle < 90))
                {
                    angle += 90;
                    if (angle > 360)
                        angle -= 360;
                }
            }
            //Third Quadrant
            else if ((objectX <= this.vPosition.X)&&(objectY >= this.vPosition.Y))
            {
                while ((angle > 270) || (angle < 180))
                {
                    angle += 90;
                    if (angle > 360)
                        angle -= 360;
                }
            }
            //Fourth Quadrant
            else
            {
                while ((angle > 360) || (angle < 270))
                {
                    angle += 90;
                    if (angle > 360)
                        angle -= 360;
                }
            }
            
            return angle;
        }
       
        ///<summary>
        ///Handles a collision for the X Axis
        ///</summary>
        public void handleXCollision()
        {
            this.vPosition.X = this.vLastPosition.X;
            this.vSpeed.X = 0;
        }

        ///<summary>
        ///Handles a collision for the Y Axis
        ///</summary>
        public void handleYCollision()
        {
            this.vPosition.Y = this.vLastPosition.Y;
            this.vSpeed.Y = 0;
        }
        #endregion

        #region Draw Funcions
        /// <summary>
        /// Draws the tank and all it's shots
        /// </summary>
        /// <param name="spriteBatch"></param>
        public override void Draw(SpriteBatch spriteBatch, Map map)
        {
            this.DrawTank(spriteBatch, map);
            this.DrawShot(spriteBatch, map);
        }

        /// <summary>
        /// Draws the tank
        /// </summary>
        /// <param name="spriteBatch"></param>
        private void DrawTank(SpriteBatch spriteBatch, Map map)
        {
            spriteBatch.Draw(this.TankSprite, map.GetTrueScreenPosition(this.vPosition), null, this.TankColor, this.Rotation, Origin, 1.0f, SpriteEffects.None, 0.0f);

            this.Turret.Draw(spriteBatch, map);
        }

        private void DrawShot(SpriteBatch spriteBatch, Map map)
        {
            this.weaponManager.Draw(spriteBatch, map);
        }
        #endregion

        /// <summary>
        /// Called when tank hit by shot
        /// </summary>
        /// <param name="shotHit">Shot that hit tank</param>
        /// <returns>Returns true if out of health</returns>
        public bool Hit(Weapons.Shot shotHit)
        {
            this.nHealth -= shotHit.Power;

            if (this.nHealth > 0)
            {
                return true;
            }

            //Tank destroyed
            return false;
        }
    }
}
